/*
 * =====================================================================================
 *
 *       Filename:  test.cpp
 *
 *    Description:  具体实现
 *
 *        Version:  1.0
 *        Created:  01/08/2018 01:43:57 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  YOUR NAME (), 
 *   Organization:  
 *
 * =====================================================================================
 */
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/stat.h>
#include <netdb.h>
#include <dirent.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <linux/if_link.h>

#include <string>
#include <vector>
#include <map>

#include "utils.h"
#include "timestat.h"
#include "ini.h"

using namespace std;

bool print_resp = false;
bool time_stat = false;;
string outputfile;
int timeout = 0;;
static CIni config;
int totalfailed = 0;
int totalpassed = 0;
string trade_server;

static string filesep("###########");

void init()
{
    string absdir = getAbsExeDir();
    if (!config.append(absdir + "config.ini"))
        return;

    string print = config.getString("service","print_response");
    if (print == "true")
        print_resp = true;
    
    string stat = config.getString("service","time_stat");
    if (stat == "true")
        time_stat = true;
    
    string timeout2 = config.getString("service","timeout");
    timeout = atoi(timeout2.c_str());
    
    outputfile = config.getString("service","outputfile");
}

void getHostAddr(vector<string>& ips)
{
	struct ifaddrs *ifaddr, *ifa;
	int family, s, n;
	char host[NI_MAXHOST] = {0};

	if (getifaddrs(&ifaddr) == -1) {
	   return ;
	}

	/* Walk through linked list, maintaining head pointer so we
	  can free list later */

	for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
		if (ifa->ifa_addr == NULL)
		   continue;

		family = ifa->ifa_addr->sa_family;

		/* Display interface name and family (including symbolic
		  form of the latter for the common families) */

		/*
		printf("%-8s %s (%d)\n",
			  ifa->ifa_name,
			  (family == AF_PACKET) ? "AF_PACKET" :
			  (family == AF_INET) ? "AF_INET" :
			  (family == AF_INET6) ? "AF_INET6" : "???",
			  family);
		*/
		/* For an AF_INET* interface address, display the address */

		if (family == AF_INET || family == AF_INET6) {
			s = getnameinfo(ifa->ifa_addr,
					(family == AF_INET) ? sizeof(struct sockaddr_in) :
										 sizeof(struct sockaddr_in6),
					host, NI_MAXHOST,
					NULL, 0, NI_NUMERICHOST);
			if (s != 0) {
				return ;
			}

			ips.push_back(host);

		} else if (family == AF_PACKET && ifa->ifa_data != NULL) {
			/*
            struct rtnl_link_stats *stats = (rtnl_link_stats*)ifa->ifa_data;

			printf("\t\ttx_packets = %10u; rx_packets = %10u\n"
				  "\t\ttx_bytes   = %10u; rx_bytes   = %10u\n",
				  stats->tx_packets, stats->rx_packets,
				  stats->tx_bytes, stats->rx_bytes);
            */
	    }
    }

	freeifaddrs(ifaddr);
}

string getHostAddr()
{
    vector<string> ips;
    getHostAddr(ips);
    vector<string>::iterator it;
    for (it = ips.begin(); it != ips.end(); ++it)
    {
        if (*it == "127.0.0.1")
            continue;
        vector<string> ipfields;
        stringSplit(*it,".",ipfields);
        if (ipfields.size() == 4)
            return *it;
    }
    return "127.0.0.1";
}

void EnumDirFiles(const string& dirPrefix,const string& dirName,vector<string>& vFiles)
{
    if (dirPrefix.empty() || dirName.empty())
        return;
    string dirNameTmp = dirName;
    string dirPre = dirPrefix;
                    
    if (dirNameTmp.find_last_of("/") != dirNameTmp.length() - 1)
        dirNameTmp += "/";
    if (dirNameTmp[0] == '/')
        dirNameTmp = dirNameTmp.substr(1);
    if (dirPre.find_last_of("/") != dirPre.length() - 1)
        dirPre += "/";

    string path;
    path = dirPre + dirNameTmp;

    struct stat fileStat;
    DIR* pDir = opendir(path.c_str());
    if (!pDir) return;

    struct dirent* pDirEnt = NULL;
    while ( (pDirEnt = readdir(pDir)) != NULL )
    {
        if (strcmp(pDirEnt->d_name,".") == 0 || strcmp(pDirEnt->d_name,"..") == 0)
            continue;

        string tmpDir = dirPre + dirNameTmp + pDirEnt->d_name;
        if (stat(tmpDir.c_str(),&fileStat) != 0)
            continue;
        
        string innerDir = dirNameTmp + pDirEnt->d_name;
        if ((fileStat.st_mode & S_IFDIR) == S_IFDIR)
        {
            EnumDirFiles(dirPrefix,innerDir,vFiles);
            continue;
        }
        
        vFiles.push_back(innerDir);
    }   
        
    if (pDir)
        closedir(pDir);
}

// return client fd
int initClientSocket(const string& server)
{
    vector<string> vserver;
    stringSplit(server,":",vserver);
    unsigned int port = 80;
    string ip = server;
    if (vserver.size() == 2)
    {
        ip = vserver[0];
        port = (unsigned int) atoi (vserver[1].c_str());
    }

    int sock;
    struct sockaddr_in addr;
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock < 0)
    {
        cerr<<"create socket failed"<<endl;
        return sock;
    }
    struct timeval tm;
    tm.tv_sec = timeout;
    tm.tv_usec = 0;

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    addr.sin_port = htons(port);

    int ret = connect(sock,(struct sockaddr *) &addr,sizeof(addr));
    if (ret != 0)
    {
        cerr<<"connect " << server << " failed"<<endl;
        return ret;
    }
    // 接收超时
    if (setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm)) != 0)
        cout<< "set recv_timeout fail"<<endl;

    return sock;
}

bool checkResult(const string& resp,const map<string,string>& checkfields,
        string& code,string& msg,fstream& f)
{
    // cljg
    size_t pos = resp.find("cljg");
    if (pos == string::npos)
    {
        cout<< "parsing cljg failed" << endl;
        if (f.is_open())
            f<< "parsing cljg failed" << "\n";
        return false;
    }

    size_t cljg_pos = pos;
    pos = resp.find("code",pos);
    if (pos == string::npos)
    {
        cout<< "parsing cljg.code failed" << endl;
        if (f.is_open())
            f<< "parsing cljg.code failed" << "\n";
        return false;
    }

    pos = resp.find(":",pos);
    pos = resp.find("\"",pos);
    size_t pos2 = resp.find("\"",pos+1);
    code = resp.substr(pos + 1,pos2 - pos - 1);

    pos = resp.find("msg",cljg_pos);
    pos = resp.find(":",pos);
    pos = resp.find("\"",pos);
    pos2 = resp.find("\"",pos+1);
    msg = resp.substr(pos + 1,pos2 - pos - 1);

    if (checkfields.empty())
    {
        cout<< "no check fields" << endl;
        if (f.is_open())
            f << "no check fields" << "\n";
        
        return true;
    }

    map<string,string>::const_iterator it;
    for(it = checkfields.begin(); it != checkfields.end(); ++it)
    {
        cout<< "checking field:" <<it->first<< endl;
        if (f.is_open())
            f<< "checking field:" <<it->first<< "\n";
        
        vector<string> vals;
        stringSplit(it->second,",",vals);
        if (vals.empty())
        {
            size_t pos3 = resp.find(it->first);
            if (pos3 == string::npos )
            {
                cout<< "no field:" <<it->first<< endl;
                if (f.is_open())
                    f<< "no field:" <<it->first<< "\n";
                return false;
            }
        }
        
        vector<string>::iterator itv;
        for (itv = vals.begin(); itv != vals.end(); ++itv)
        {
            size_t vpos = 0;
            bool bcheck = false;
            cout<< "checking value:" <<*itv<< "\t:";
            if (f.is_open())
                f<< "checking value:" <<*itv<< "\t:";
            while( (vpos = resp.find(it->first,vpos)) != string::npos)
            {
                vpos = resp.find(":",vpos);
                vpos = resp.find("\"",vpos);
                size_t vpos2 = resp.find("\"",vpos+1);
                string vval = resp.substr(vpos + 1,vpos2 - vpos - 1);
                if (vval == it->second)
                {
                    cout<< "passed" << endl;
                    if (f.is_open())
                        f<< "passed" << "\n";

                    bcheck = true;
                    break;
                }
            }

            if (!bcheck)
            {
                cout<< "failed" << endl;
                if (f.is_open())
                    f<< "failed" << "\n";
                return false;
            }
        }
    }

    return true;
}

void test_one_file(const string& file)
{
    cout<<filesep<<" testing " << file << " " << filesep << endl;
    map<string,map<string,string> > sections;
    CIni tmpIni;
    if (!tmpIni.append(file))
    {
        cout<< file << " load failed" << endl;
        return;
    }

    tmpIni.enumAllSection(sections);
    map<string,map<string,string> >::iterator it;

    //map<string,string>& errorcode = sections["errorcode"];
    fstream f(outputfile.c_str(),ios_base::out|ios_base::app|ios_base::ate);
    
    if (f.is_open())
        f<<filesep<<" testing " << file << " " << filesep << endl;
    
    for (it = sections.begin(); it != sections.end(); ++it)
    {
        if (it->first == "errorcode")
            continue;
        
        if (it->first.find(".check") != string::npos)
            continue;
        
        cout<<endl;
        f<<"\n";
        string& url = it->second["url"];
        if (url.empty())
        {
            cout<< "skip, url is empty"<<endl;
            if (f.is_open())
                f << "skip, url is empty"<<endl;
            continue;
        }
        
        string& req = it->second["input"];
        if ((req.size() == 2
            && req[0] == '\"'
            && req[req.size() - 1] == '\"')
            || req.empty()
            || req.size() < 2)
        {
            cout<< "skip, input is empty"<<endl;
            if (f.is_open())
                f << "skip, input is empty"<<endl;
            continue;
        }
        
        map<string,string> seccheck;
        tmpIni.enumSection(it->first + ".check",seccheck);
        
        if (req.size() > 2
            && req[0] == '\"'
            && req[req.size() - 1] == '\"')
        {
            req = req.substr(1,req.size() - 1);
        }
        
        string sendStr;
        sendStr.reserve(512);
        sendStr = "POST " + url;
        sendStr += " HTTP/1.0\nHost: ";
        sendStr += getHostAddr() + "\n"
        "Content-Type:application/json; charset:utf-8\n"
        "Accept:application/json; charset:utf-8\n";
        string& cookie = it->second["cookie"];
        if (!cookie.empty())
        {
            sendStr += "Cookie:" + cookie + "\n";
        }
        char buf[4096] = {'\0'};
        sprintf(buf,"Content-Length:%lu",req.length());
        sendStr += string(buf) + "\n\n";
        sendStr += req;
        sendStr += "\r\n\r\n";
        cout<< file<<"."<<it->first<<endl;
        cout<< "req: " << endl;
        cout<< sendStr << endl;

        CTimeStat ts;
        ts.AddStep("begin");
        int fd = initClientSocket(trade_server);
        if (fd < 0)
        {
            cout<< "skip,init socket failed "<<endl;
            if (f.is_open())
                f << "skip,init socket failed "<<endl;
            continue;
        }
        
        int ret = send(fd,sendStr.c_str(),sendStr.length(),0);
        if (ret != (int)sendStr.length())
        {
            cout<< "post "<< it->first << " failed"<<endl;
            if (f.is_open())
                f << "post "<< it->first << " failed"<<"\n";
            continue;
        }
        cout<< "send req success"<<endl;
        string resp;
        
        resp.reserve(4096);
        int bufsize = sizeof(buf);
        int recvb = 0;
        memset(buf,0,bufsize);

        while( (recvb = recv(fd,buf,bufsize - 1,0)) > 0 )
        {
            cout<<"recv bytes:"<<recvb<<endl;
            if (f.is_open())
                f<<"recv bytes:"<<recvb<<endl;

            resp += string(buf);
            memset(buf,0,bufsize);
        }
        cout<<endl;

        ts.AddStep("end");
        string code,msg;
        bool passed = checkResult(resp,seccheck,code,msg,f);
        double timediff = ts.GetInterval("end","begin",UNIT_USECOND);
        if (print_resp)
        {
            cout<< "time used:" << timediff << endl;
            if (passed)
                cout << "case passed:\ncode:"<<code<<"\nmsg:"<<msg<<"\n";
            else
                cout << "case failed:\ncode:"<<code<<"\nmsg:"<<msg<<"\n";
            
            cout<< "resp:\n" << resp << endl;
        }
        
        if (f.is_open())
        {
            f << file<<"."<<it->first<<endl;
            f << "req: " << endl;
            f << sendStr << endl;
            f << "time used:"<< timediff <<"\n";
            if (passed)
                f << "case passed:\ncode:"<<code<<"\nmsg:"<<msg<<"\n";
            else
                f << "case failed:\ncode:"<<code<<"\nmsg:"<<msg<<"\n";
            f << "resp:\n" << resp <<"\n";
        }

        if (passed)
            ++totalpassed;
        else
            ++totalfailed ;

        //case end
        cout<<endl;
        if (f.is_open())
            f << "\n";
        close(fd);
    }
    
    cout<<endl;
    if (f.is_open())
        f << "\n";
    
    f.close();
}

void test_dir(const string& dir)
{
    string tmpdir = dir;
    if (!tmpdir.empty() 
            && tmpdir[tmpdir.size()-1] == '/')
    {
        tmpdir.resize(tmpdir.size()-1);
    }
    size_t pos = tmpdir.find_last_of("/");
    string dirName,dirPrefix;
    if (pos != string::npos)
    {
        dirName     = tmpdir.substr(pos + 1);
        dirPrefix   = tmpdir.substr(0,pos);
    }
    
    vector<string> vFiles;
    EnumDirFiles(dirPrefix,dirName,vFiles);
    
    vector<string>::iterator it;
    for (it = vFiles.begin(); it != vFiles.end(); ++it)
    {
        test_one_file(*it);
    }
}

void test(const string& server,const vector<string>& files)
{
    init();

    trade_server = !server.empty() ? server : 
        config.getString("service","server");

    fstream f(outputfile.c_str(),ios_base::out|ios_base::trunc);
    if (f.is_open())
    {
        f.close();
    }
    
    vector<string>::const_iterator itf;
    for ( itf = files.begin(); itf != files.end(); ++itf)
    {
        struct stat fileInfo;
        stat(itf->c_str(), &fileInfo);

        if (S_ISREG(fileInfo.st_mode))
        {
            test_one_file(*itf);
        }
        else if (S_ISDIR(fileInfo.st_mode))
        {
            string absdir;
            if ((*itf)[0] != '/')
            {
                absdir = getAbsExeDir();
                absdir += *itf;
                test_dir(absdir);
            }
            else    // 已经是绝对路径
            {
                test_dir(*itf);
            }

        }
        else
        {
            cout<<"open file failed:"<<*itf<<endl;
            return;
        }
    }
    
    fstream f2(outputfile.c_str(),ios_base::out|ios_base::app|ios_base::ate);
    if (f2.is_open())
    {
        f2<<"total passed:"<<totalpassed<<"\n";
        f2<<"total failed:"<<totalfailed<<"\n";
        f2<<"\n";
        f2.close();
    }
    cout<<"total passed:"<<totalpassed<<"\n";
    cout<<"total failed:"<<totalfailed<<"\n";
    cout<<endl;
}















